//****************************************Copyright (c)***********************************//
//Copyright(C) 正点原子 2018-2028
//All rights reserved                                  
//----------------------------------------------------------------------------------------
// File name:           udp_tx
// Last modified Date:  2023/11/9
// Copy & modified:     写的bug都队
// Last Version:        V2.0
// Descriptions:        以太网数据发送模块
//----------------------------------------------------------------------------------------
// Author :             正点原子
// Created date:        2020/2/18 9:20:14
//****************************************************************************************//

//*****************************************************
//**        以太网待发送数据: 32bit -> 8bit
//*****************************************************

module udp_tx#(
	parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff, 
	parameter BOARD_MAC = {8'h06, 8'h00, 8'haa, 8'hbb, 8'h0c, 8'hdd}, 
	parameter DES_IP = {8'd192,8'd168,8'd15,8'd15}, 
	parameter BOARD_IP = {8'd192,8'd168,8'd15,8'd14}, 
	parameter DEST_UDP_PORT = 16'd5678, 
	parameter SRC_UDP_PORT = 16'd1234 
)(
  input                     clk             ,                  //50M时钟输入
  input                     rst_n           ,                  //复位
  input                     tx_start_en     ,                  //以太网开始发送信号
  input     [31:0]          tx_data         ,                  //以太网待发送数据
  input     [15:0]          tx_byte_num     ,                  //以太网发送的有效字节数

  output  reg               tx_done         ,                  //以太网发送完成信号
  output  reg               tx_req          ,                  //读数据请求信号
  output  reg               rmii_tx_en      ,                  //RMII输出数据有效信号（改自gmii_tx_en）
  output  reg  [7:0]        rmii_tx_data                       //RMII传输数据  (改自gmii_txd)

);



//parameter define
//开发板MAC地址 06-00-aa-bb-0c-dd
//parameter BOARD_MAC = {8'h06, 8'h00, 8'haa, 8'hbb, 8'h0c, 8'hdd};
//目的MAC地址 ff_ff_ff_ff_ff_ff
//parameter DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//开发板IP地址 192.168.15.14  
//parameter BOARD_IP  = {8'd192,8'd168,8'd15,8'd14};  
//目的IP地址 192.168.15.15     
//parameter DES_IP    = {8'd192,8'd168,8'd15,8'd15};


localparam  ETH_TYPE     = 16'h0800   ; //以太网协议类型 IP协议
//以太网数据最小46个字节，IP首部20个字节+UDP首部8个字节
//所以数据至少46-20-8=18个字节
localparam  MIN_DATA_NUM = 16'd18     ;  
localparam  UDP_TYPE    = 8'd17       ; //UDP协议类型  

//状态机参数：魔改版本，前导码+SFD,CRC校验已由官方例程代码实现
localparam  st_idle      = 7'b000_0001; //初始状态，等待开始发送信号
localparam  st_check_sum = 7'b000_0010; //IP首部校验和
// localparam  st_preamble  = 7'b000_0100; //发送前导码+帧起始界定符
localparam  st_eth_head  = 7'b000_1000; //发送以太网帧头
localparam  st_ip_head   = 7'b001_0000; //发送IP首部+UDP首部
localparam  st_tx_data   = 7'b010_0000; //发送数据
// localparam  st_crc       = 7'b100_0000; //发送CRC校验值

//reg define
reg  [6:0]   cur_state      ;
reg  [6:0]   next_state     ;
                            
reg  [7:0]   preamble[7:0]  ; //前导码
reg  [7:0]   eth_head[13:0] ; //以太网首部
reg  [31:0]  ip_head[6:0]   ; //IP首部 + UDP首部
                            
reg          start_en_d0    ;
reg          start_en_d1    ;
reg  [15:0]  tx_data_num    ; //发送的有效数据字节个数
reg  [15:0]  total_num      ; //总字节数
reg          trig_tx_en     ;
reg  [15:0]  udp_num        ; //UDP字节数
reg          skip_en        ; //控制状态跳转使能信号
reg  [4:0]   cnt            ; //发送状态机通用计数器
reg  [31:0]  check_buffer   ; //首部校验和
reg  [1:0]   tx_byte_sel    ; //32位数据转8位数据计数器
reg  [15:0]  data_cnt       ; //发送数据个数计数器
reg          tx_done_t      ;
reg  [4:0]   real_add_cnt   ; //以太网数据实际多发的字节数
                                 
//wire define                       
wire         pos_start_en    ;//开始发送数据上升沿
wire [15:0]  real_tx_data_num;//实际发送的字节数(以太网最少字节要求)


/*采tx_start_en的上升沿*/
assign  pos_start_en = (~start_en_d1) & start_en_d0;
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        start_en_d0 <= 1'b0;
        start_en_d1 <= 1'b0;
    end    
    else begin
        start_en_d0 <= tx_start_en;
        start_en_d1 <= start_en_d0;
    end
end 

/*寄存数据有效字节*/
assign  real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) ? tx_data_num : MIN_DATA_NUM; 
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_data_num <= 16'd0;
        total_num <= 16'd0;
        udp_num <= 16'd0;
    end
    else begin
        if(pos_start_en && cur_state==st_idle) begin
            //数据长度
            tx_data_num <= tx_byte_num;     
            //UDP长度：UDP首部长度 + 有效数据            
            udp_num <= tx_byte_num + 16'd8;               
            //IP长度：IP首部长度 + UDP首部 + 有效数据             
            total_num <= tx_byte_num + 16'd20 + 16'd8;  
        end    
    end
end

/*触发发送信号*/
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        trig_tx_en <= 1'b0;
    else
        trig_tx_en <= pos_start_en;

end

/*状态机状态改变*/
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;  
    else
        cur_state <= next_state;
end

always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle     : begin                               //等待发送数据
            if(skip_en)                
                next_state = st_check_sum;
            else
                next_state = st_idle;
        end  
        st_check_sum: begin                               //IP首部校验
            if(skip_en)
                next_state = st_eth_head;
            else
                next_state = st_check_sum;    
        end                             
        st_eth_head : begin                               //发送以太网首部
            if(skip_en)
                next_state = st_ip_head;
            else
                next_state = st_eth_head;      
        end              
        st_ip_head : begin                                //发送IP首部+UDP首部               
            if(skip_en)
                next_state = st_tx_data;
            else
                next_state = st_ip_head;      
        end
        st_tx_data : begin                                //发送数据                  
            if(skip_en)
                next_state = st_idle;  //CRC部分由rmii_tx部分完成
            else
                next_state = st_tx_data;      
        end
        // st_crc: begin                                     //发送CRC校验值
        //     if(skip_en)
        //         next_state = st_idle;
        //     else
        //         next_state = st_crc;      
        // end
        default : next_state = st_idle;   
    endcase
end   

/*数据打包发送*/
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin  //复位
        //参数复位
        skip_en <= 1'b0; 
        cnt <= 5'd0;
        check_buffer <= 32'd0;
        ip_head[1][31:16] <= 16'd0;
        tx_byte_sel <= 2'b0;
        // crc_en <= 1'b0; //--
        rmii_tx_en <= 1'b0;
        rmii_tx_data <= 8'd0;
        tx_req <= 1'b0;
        tx_done_t <= 1'b0;
        data_cnt <= 16'd0;
        real_add_cnt <= 5'd0;
        //初始化数组
        //目的MAC地址
        eth_head[0] <= DES_MAC[47:40];
        eth_head[1] <= DES_MAC[39:32];
        eth_head[2] <= DES_MAC[31:24];
        eth_head[3] <= DES_MAC[23:16];
        eth_head[4] <= DES_MAC[15:8];
        eth_head[5] <= DES_MAC[7:0];
        //源MAC地址
        eth_head[6] <= BOARD_MAC[47:40];
        eth_head[7] <= BOARD_MAC[39:32];
        eth_head[8] <= BOARD_MAC[31:24];
        eth_head[9] <= BOARD_MAC[23:16];
        eth_head[10] <= BOARD_MAC[15:8];
        eth_head[11] <= BOARD_MAC[7:0];
        //以太网类型
        eth_head[12] <= ETH_TYPE[15:8];
        eth_head[13] <= ETH_TYPE[7:0]; 
    end
    else begin
        skip_en <= 1'b0;
        tx_req <= 1'b0;
        rmii_tx_en <= 1'b0;
        tx_done_t <= 1'b0;
        case(next_state)
            st_idle: begin
                if(trig_tx_en) begin  //发送使能后
                    skip_en <= 1'b1;  //可以跳到下一个状态了
                    //ip头赋值
                    //版本号：4 首部长度：5(单位:32bit,20byte/4=5)
                    ip_head[0] <= {8'h45,8'h00,total_num};
                    //16位标识，每次发送累加1      
                    ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1; 
                    //bit[15:13]: 010表示不分片
                    ip_head[1][15:0] <= 16'h4000;  
                    //协议：17(udp)                  
                    ip_head[2] <= {8'h40,UDP_TYPE,16'h0};
                    //源IP地址               
                    ip_head[3] <= BOARD_IP;

                    //目的IP地址 (这部分是在有arp的情况下使用，这里直接用固定ip)   
                    // if(des_ip != 32'd0)
                    //     ip_head[4] <= des_ip;
                    // else
                    ip_head[4] <= DES_IP;
                    //16位源端口号：1234  16位目的端口号：5678                      
//                    ip_head[5] <= {16'd1234,16'd5678};  
                    ip_head[5] <= {SRC_UDP_PORT,DEST_UDP_PORT};  
                    //16位udp长度，16位udp校验和（udp校验和赋0，忽略）             
                    ip_head[6] <= {udp_num,16'h0000}; 

                    //arp情况下更新mac地址，略
                end
            end
            st_check_sum: begin  //IP首部校验
              cnt <= cnt + 5'd1;
                if(cnt == 5'd0) begin                   
                    check_buffer <= ip_head[0][31:16] + ip_head[0][15:0]
                                    + ip_head[1][31:16] + ip_head[1][15:0]
                                    + ip_head[2][31:16] + ip_head[2][15:0]
                                    + ip_head[3][31:16] + ip_head[3][15:0]
                                    + ip_head[4][31:16] + ip_head[4][15:0];
                end
                else if(cnt == 5'd1)                      //可能出现进位,累加一次
                    check_buffer <= check_buffer[31:16] + check_buffer[15:0];
                else if(cnt == 5'd2) begin                //可能再次出现进位,累加一次
                    check_buffer <= check_buffer[31:16] + check_buffer[15:0];
                end                             
                else if(cnt == 5'd3) begin                //按位取反 
                    skip_en <= 1'b1;
                    cnt <= 5'd0;            
                    ip_head[2][15:0] <= ~check_buffer[15:0];
                end  
            end
            st_eth_head : begin                           //发送以太网首部
              rmii_tx_en <= 1'b1;
              // crc_en <= 1'b1;  //?
              rmii_tx_data <= eth_head[cnt];
              if (cnt == 5'd13) begin
                  skip_en <= 1'b1;
                  cnt <= 5'd0;
              end    
              else    
                  cnt <= cnt + 5'd1;
            end
            st_ip_head  : begin                           //发送IP首部 + UDP首部
              // crc_en <= 1'b1;
              rmii_tx_en <= 1'b1;
              tx_byte_sel <= tx_byte_sel + 2'd1;
              if(tx_byte_sel == 2'd0)
                  rmii_tx_data <= ip_head[cnt][31:24];
              else if(tx_byte_sel == 2'd1)
                  rmii_tx_data <= ip_head[cnt][23:16];
              else if(tx_byte_sel == 2'd2) begin
                  rmii_tx_data <= ip_head[cnt][15:8];
                  if(cnt == 5'd6) begin
                      //提前读请求数据，等待数据有效时发送
                      tx_req <= 1'b1;                     
                  end
              end 
              else if(tx_byte_sel == 2'd3) begin  //2bit3再+1就直接归零，不用手动清零了
                  rmii_tx_data <= ip_head[cnt][7:0];  
                  if(cnt == 5'd6) begin
                      skip_en <= 1'b1;   
                      cnt <= 5'd0;
                  end    
                  else
                      cnt <= cnt + 5'd1;  
              end
            end
            st_tx_data  : begin                           //发送数据  从tx_en上升沿开始到这里4个clk？
              // crc_en <= 1'b1;
              rmii_tx_en <= 1'b1;
              tx_byte_sel <= tx_byte_sel + 2'd1;  //字节选择计数器自加
              if(tx_byte_sel == 1'b0)
                  rmii_tx_data <= tx_data[31:24];
              else if(tx_byte_sel == 2'd1)
                  rmii_tx_data <= tx_data[23:16];                   
              else if(tx_byte_sel == 2'd2) begin
                  rmii_tx_data <= tx_data[15:8];   
                  if(data_cnt != tx_data_num - 16'd2)
                      tx_req <= 1'b1;  
              end
              else if(tx_byte_sel == 2'd3)
                  rmii_tx_data <= tx_data[7:0];    
              if(data_cnt < tx_data_num - 16'd1)  //tx_data_num是udp数据的字节数  
                  data_cnt <= data_cnt + 16'd1;                        
              else if(data_cnt == tx_data_num - 16'd1)begin  //传输结束
                  //如果发送的有效数据少于18个字节，在后面填补充位
                  //填充的值为0
                  tx_req <= 1'b0;
                  if(data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
                      real_add_cnt <= real_add_cnt + 5'd1;  
                  else begin
                      skip_en <= 1'b1;
                      data_cnt <= 16'd0;
                      real_add_cnt <= 5'd0;
                      tx_byte_sel <= 2'd0;   
                      tx_done_t <= 1'b1;      //传输完成
                  end   
                  if(real_add_cnt > 0) begin
                      rmii_tx_data <= 8'd0;
                  end    
              end 
            end
            default :;
        endcase
    end
end

//发送完成信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_done <= 1'b0;
    end
    else begin
        tx_done <= tx_done_t;
    end
end



endmodule